/*
 * Copyright 2007 Rob Nielsen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.proxymatic.apps.autoboundary.handlers;

import com.googlecode.proxymatic.apps.autoboundary.AutoboundaryLookup;
import com.googlecode.proxymatic.core.BuildtimeContext;
import com.googlecode.proxymatic.core.ProxyBuilder;
import com.googlecode.proxymatic.core.ProxyConfiguration;
import com.googlecode.proxymatic.core.RuntimeContext;

import java.lang.reflect.Array;

public class AutoboundaryWrapperUnwrapper {
    private ProxyBuilder proxyBuilder;
    private final AutoboundaryLookup autoboundaryLookup;

    public AutoboundaryWrapperUnwrapper(ProxyBuilder proxyBuilder, AutoboundaryLookup autoboundaryLookup) {
        this.proxyBuilder = proxyBuilder;
        this.autoboundaryLookup = autoboundaryLookup;
    }

    public Object getObject(Class requiredClass, Class providedType, Object providedObject) {
        if (providedObject == null) {
            return null;
        }
        assert providedType.isInstance(providedObject);
        if (providedType.isArray()) {
            Object[] objects = (Object[]) providedObject;
            Object[] newObjects = (Object[]) Array.newInstance(requiredClass.getComponentType(), objects.length);
            for (int j = 0; j < objects.length; j++) {
                Object o = getObject(requiredClass.getComponentType(), providedType.getComponentType(), objects[j]);
                newObjects[j] = o;
            }
            return newObjects;
        }
        return getObjectInternal(requiredClass, providedType, providedObject);
    }

    private Object getObjectInternal(Class requiredClass, Class providedType, Object providedObject) {
        Object object = tryToUnwrapObject(requiredClass, providedObject);
        if (object == null) {
            object = tryToWrapObject(requiredClass, providedType, providedObject);
        }
        if (object == null) {
            throw new IllegalArgumentException("Error! '" + providedObject + "' is not a autoboundary proxy class and " + requiredClass + " is not an interface.");
        }
        return object;
    }

    private Object tryToUnwrapObject(Class requiredClass, Object providedObject) {
        ProxyConfiguration configuration = proxyBuilder.getProxyEngine().getProxyConfiguration(providedObject);
        if (configuration != null) {
            RuntimeContext runtimeContext = configuration.getRuntimeContext();
            BuildtimeContext buildtimeContext = runtimeContext.getBuildtimeContext();
            if (proxyBuilder.equals(configuration.getProxyBuilder()) &&
                    buildtimeContext.isImplementationAvailableAtRuntime() &&
// FIX ROB Test is assignable from
                    requiredClass.isAssignableFrom(buildtimeContext.getImplementationClass())) {
                return runtimeContext.getImplementationObject();
            }
        }
        return null;
    }

    private Object tryToWrapObject(Class requiredClass, Class providedType, Object providedObject) {
        if (requiredClass.isInterface()) {
            Class realProvidedType = getRealClass(requiredClass, providedType);
            return proxyBuilder.create(proxyBuilder.proxy().implementingInterface(requiredClass)
                    .withImplementationClass(realProvidedType)
                    .withImplementationObject(providedObject));
        }
        return null;
    }

    public void ensureHandled(Class targetInterface, Class implementationClass) {
        if (implementationClass.isArray() && targetInterface.isArray()) {
            ensureHandled(targetInterface.getComponentType(), implementationClass.getComponentType());
        }
        if (targetInterface.isInterface()) {
            Class impl = getRealClass(targetInterface, implementationClass);
            proxyBuilder.ensureHandled(new BuildtimeContext(targetInterface, impl, true));
        }
    }

    private Class getRealClass(Class targetInterface, Class implementationClass) {
        if (autoboundaryLookup.isAutoboundaryClass(targetInterface)) {
            return autoboundaryLookup.getRealClassForInstance(targetInterface);
        } else {
            return implementationClass;
        }
    }
}
